/*
 * Copyright (c) 2007
 *	Alberto Magni, Maggioni Marcello
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

package Database.XMLManager;

import Database.UsersDB;
import exceptions.DatabaseException;
import java.io.File;
import java.security.MessageDigest;
import java.util.ArrayList;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 *  This class is the implementation of the Users database using a XML representation of data.
 *  
 * @author Marcello Maggioni
 */
public class XMLUsersFile extends XMLFile implements UsersDB {

    private XMLUsersFile(String filepath) throws DatabaseException, ParserConfigurationException, TransformerConfigurationException {
        super(filepath);
        if (!exists())
            init();
    }

    public void addUser(String username, String password, String name) throws DatabaseException {
        synchronized (this) {
            Document doc = readFile();
            Element el = doc.getDocumentElement();
            NodeList nl = doc.getElementsByTagName("user");
            Element user = getUser(nl, username);
            if (user != null) {
                throw new DatabaseException("This username already exists in the database. Choose a different one!!");
            }
            user = doc.createElement("user");
            user.setAttribute("enabled", "false");
            user.setAttribute("admin", "false");
            user.setAttribute("password", calculateHash(password));
            user.setAttribute("username", username);
            user.setAttribute("name", name);
            el.appendChild(user);
            writeFile(doc);
        }
    }

    public void enableUser(String username) throws DatabaseException{
        Document doc = null;
        synchronized (this) {
            doc = readFile();
            NodeList nl = doc.getElementsByTagName("user");
            Element el = getUser(nl, username);
            el.setAttribute("enabled", "true");
            writeFile(doc);
        }
    }

    public void disableUser(String username) throws DatabaseException {
        Document doc = null;
        synchronized (this) {
            doc = readFile();
            NodeList nl = doc.getElementsByTagName("user");
            Element el = getUser(nl, username);
            el.setAttribute("enabled", "false");
            writeFile(doc);
        }
    }

    public String getName(String username) throws DatabaseException {
        Document doc = null;
        synchronized (this) {
            doc = readFile();
        }
        NodeList nl = doc.getElementsByTagName("user");
        Element el = getUser(nl, username);
        return el.getAttribute("name");
    }
    
    public void deleteUser(String username) throws DatabaseException {
        Document doc = null;
        synchronized (this) {
            doc = readFile();

            NodeList nl = doc.getElementsByTagName("user");
            Element el = getUser(nl, username);
            el.getParentNode().removeChild(el);
            writeFile(doc);
        }
    }

    public void setAdmin(String username, boolean value) throws DatabaseException {
        Document doc = null;
        synchronized (this) {
            doc = readFile();
            NodeList nl = doc.getElementsByTagName("user");
            Element el = getUser(nl, username);
            if (value) {
                el.setAttribute("admin", "true");
            } else {
                el.setAttribute("admin", "false");
            }
            writeFile(doc);
        }
    }

    public boolean isAdmin(String username) throws DatabaseException {
        Document doc = null;
        doc = readFile();
        NodeList nl = doc.getElementsByTagName("user");
        Element el = getUser(nl, username);
        if (el == null) {
            return false;
        }
        if (!el.getAttribute("admin").equals("true")) {
            return false;
        }

        return true;
    }
    
    public String[] getEnabledUsers() throws DatabaseException {
        Document doc = null;
        synchronized (this) {
            doc = readFile();
        }
        NodeList nl = doc.getElementsByTagName("user");
        ArrayList<String> enabled = new ArrayList<String>();
        for (int count = 0; count < nl.getLength(); count++) {
            Element item = (Element) nl.item(count);
            if (item.getAttribute("enabled").equals("true")) {
                enabled.add(item.getAttribute("username"));
            }
        }

        return enabled.toArray(new String[0]);
    }
    
    public String[] getDisabledUsers() throws DatabaseException {
        Document doc = null;
        synchronized (this) {
            doc = readFile();
        }
        Element el = doc.getDocumentElement();
        NodeList nl = el.getElementsByTagName("user");
        ArrayList<String> disabled = new ArrayList<String>();
        for (int count = 0; count < nl.getLength(); count++) {
            Element item = (Element) nl.item(count);
            if (item.getAttribute("enabled").equals("false")) {
                disabled.add(item.getAttribute("username"));
            }
        }

        return disabled.toArray(new String[0]);
    }
    /*
    public void changeUsername(String olduser, String newuser) throws DatabaseException {
        Document doc = null;
        synchronized (this) {
            doc = readFile();
            NodeList nl = doc.getElementsByTagName("user");
            Element el = getUser(nl, newuser);
            if (el != null) {
                throw new DatabaseException("The username you tried to use already exists. Please choose a different one");
            }
            el = getUser(nl, olduser);
            if (el == null)
                throw new DatabaseException("No username " + olduser);
            el.setAttribute("username", newuser);
            writeFile(doc);
        }
    }
    */
    public void changePassword(String username, String newpassword) throws DatabaseException {
        Document doc = null;
        synchronized (this) {
            doc = readFile();
            NodeList nl = doc.getElementsByTagName("user");
            Element el = getUser(nl, username);
            el.setAttribute("password", calculateHash(newpassword));
            writeFile(doc);
        }
    }
    
    public boolean verifyUser(String username, String password) throws DatabaseException {
        Document doc = null;
       synchronized(this) { 
           doc = readFile();
       }
        NodeList nl = doc.getElementsByTagName("user");
        Element el = getUser(nl, username);
        if (el == null) {
            return false;
        }
        if (!el.getAttribute("enabled").equals("true")) {
            return false;
        }
        if (!el.getAttribute("password").equals(calculateHash(password))) {
            return false;
        }

        return true;
    }
    /**
     * Returns a default instance of an XMLUsersFile object 
     *  
     * @param root_path The root path in which the file will be stored
     * @return An XMLUsersFile instance
     */
    static public UsersDB getDefaultInstance(String root_path) throws DatabaseException {
        try {
            File rp = new File(root_path);
            if(!rp.exists())
                rp.mkdirs();
            return new XMLUsersFile(root_path + File.separator + "users.xml");
        } catch (Exception ex) {
            throw new DatabaseException(ex.getMessage());
        } 
    }
    
    static private Element getUser(NodeList nl, String username) {
        for (int count = 0; count < nl.getLength(); count++) {
            Element item = (Element) nl.item(count);
            if(item.getAttribute("username").equals(username))
                return item;
        }

        return null;
    }

    private String calculateHash(String str) throws DatabaseException {
        String dig = null;
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(str.getBytes("CP1252"));
            dig = toHex(digest);
        } catch (Exception ex) {
            throw new DatabaseException(ex.getMessage());
        } 
        return dig;
    }

    private static String toHex(byte[] array) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < array.length; i++) {
            sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).toUpperCase().substring(1, 3));
        }
        return sb.toString();
    }
    
    private void init() throws DatabaseException {
        try {
            super.initialize();
            Document d = readFile();
            Element messages = d.createElement("users");
            d.appendChild(messages);
            writeFile(d);
            addUser("admin", "adminadmin", "Admin");
            enableUser("admin");
            setAdmin("admin", true);
        } catch (Exception ex) {
            throw new DatabaseException(ex.getMessage());
        } 
    }

}
